#pragma once 
#include<iostream>
#include<string>
#include<memory>
#include "Socket.hpp"
using namespace socket_ns;


//select 服务器要正确的编写要借助一个第三方数组来完成,保存合法的 所有的fd到数组中,方便后期批量化同一处理
class SelectServer
{
    const static int N = sizeof(fd_set)*8;
    const static int defaultfd = -1;
    public:
        SelectServer(uint16_t port)
        :_port(port)
        ,_listensock(std::make_unique<TcpSocket>())//父类指针指向子类对象
        {
            InetAddr addr("0", _port);
            for(int i = 0; i < N; i++)
            {
                _fd_array[i] = defaultfd;
            }
            _fd_array[0] = _listensock->SockFd();
        }
        void AcceptClient()
        {
            //只关心读: 读有 监听套接字 和 通信套接字
            InetAddr clientaddr;
            int sockfd = _listensock->Accepter(&clientaddr);//这里调用Accepter不会阻塞,因为事件已经就绪,即:已完成连接队列中一定有连接
            if(sockfd < 0)
            {
                return ;
            }
            LOG(DEBUG, "Get new Link, sockfd: %d, client info %s: %d\n", sockfd, clientaddr.Ip().c_str(), clientaddr.Port());
            //read/recv(sockfd); send(sockfd) 不能,必须将新的文件描述符托管给select,
            //将通信文件描述符添加到数组中
            int pos = 1; 
            for(; pos < N; pos++)
            {
                if(_fd_array[pos] == defaultfd)
                {
                    break;
                }
            }
            if(pos == N)
            {
                //此时表示监听的文件描述符已满
                ::close(sockfd);
                LOG(WARNING, "select fd is full\n");
                return ;
            }
            else 
            {
                _fd_array[pos] = sockfd;
                LOG(INFO, "%d add to select array!\n");
            }
            LOG(INFO, "current fd_array[] fd list: %s \n", RfdsToString().c_str());
        }
        void Service(int pos)
        {
            char buffer[1024];
            ssize_t n = recv(_fd_array[pos], buffer, sizeof(buffer) - 1, 0);
            if(n > 0)
            {
                buffer[n] = 0;
                std::cout<<"client echo# " << buffer<< std::endl;
                std::string echo_str = "[server echo]# ";
                echo_str += buffer;
                ::send(_fd_array[pos], echo_str.c_str(), echo_str.size(), 0);
            }
            else if(n == 0)
            {
                LOG(DEBUG, "%d is closed\n", _fd_array[pos]);
                ::close(_fd_array[pos]);
                _fd_array[pos] = defaultfd;
                LOG(DEBUG, "current fd_array[] fd list : %s \n", RfdsToString().c_str());
            }
            else 
            {
                LOG(DEBUG, "%d recv error\n", _fd_array[pos]);
                ::close(_fd_array[pos]);
                _fd_array[pos] = defaultfd;
                LOG(DEBUG, "current fd_array[] fd list : %s \n", RfdsToString().c_str());
            }

        }
        void HandlerEvent(fd_set& rfds)
        {
            //遍历rfds,判断事件类型
            for(int i = 0; i < N; i++)
            {
                if(_fd_array[i] == defaultfd)
                {
                    //这表示没有设置对该事件的关心,直接跳过
                    continue;
                }
                if(FD_ISSET(_fd_array[i], &rfds))
                {
                    //此时关心的文件描述符有事件发生了
                    //判断事件的类型
                    if(_fd_array[i] == _listensock->SockFd())
                    {
                        //此时是新连接到来,从已连接队列中取出,返回通信文件描述符
                        AcceptClient();
                    }
                    else
                    {
                        Service(i);
                    }
                }
            }
        }
        void Loop()
        {
            while(true)
            {
                //listensocket 等待新连接的到来 等价与对方给我发信息,我们作为读事件统一处理
                //新连接到来等价与 读事件就绪
                //首先 将listensocket添加到 select中
                fd_set rfds;
                FD_ZERO(&rfds);
                int max_fd = defaultfd;
                //遍历辅助数组添加读事件
                for(int i = 0; i < N; i++)
                {
                    if(_fd_array[i] == defaultfd)
                    {
                        continue;
                    }
                    FD_SET(_fd_array[i], &rfds);
                    //更新最大文件描述符
                    if(_fd_array[i] > max_fd)
                    {
                        max_fd = _fd_array[i];
                    }
                }

                //#include <sys/select.h>
                /*
                nfds: 表示监视的最大文件描述符+1;
                readfds: / writefds / exceptfds : 分别指向可读,可写,异常事件的fd集合, 这三个都是输入输出型参数即 输入数据, 输出结果
                timeout: 超时时间,设置NULL为永久阻塞,设置为0表示立即返回(非阻塞)
                */
                // int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

                //设置超时时间
                struct timeval timeout = {0, 0};
                int n = select(max_fd+1, &rfds, NULL, NULL, NULL);
                switch (n)
                {
                case 0:
                    LOG(INFO, "timeout, %d.%d\n", timeout.tv_sec,timeout.tv_usec);
                    break;
                case -1:
                    LOG(ERROR, "select error\n");
                    break;
                default:
                    LOG(INFO, "Event Happen, n : %d\n", n);
                    HandlerEvent(rfds);
                    break;
                }
                
            }
        }
        std::string RfdsToString()
        {
            //此函数将_fd_array中的文件描述符转换为字符串方便打印
            std::string fd_string;
            for(int i = 0; i < N; i++)
            {
                if(_fd_array[i] == defaultfd)
                {
                    continue;
                }
                fd_string += std::to_string(_fd_array[i]);
                fd_string += " ";
            }
            return fd_string;
        }
        private:
        uint16_t _port;          //端口号
        std::unique_ptr<Socket> _listensock;
        int _fd_array[N];        //辅助数组       
};
